package com.google.code.coma.assembly;

import com.google.code.coma.Coma;
import com.google.code.coma.domain.Computer;
import com.google.code.coma.domain.Order;
import com.google.code.coma.domain.Part;
import com.google.code.coma.rmi.*;
import com.google.code.coma.rmi.storage.StorageService;
import com.google.code.coma.worker.Worker;
import org.apache.log4j.Logger;

import java.rmi.NoSuchObjectException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.List;

/**
 */
public class RmiAssemblyWorker extends AbstractAssemblyWorker implements Worker {
	private static final Logger log = Logger.getLogger(RmiAssemblyWorker.class);

	private StorageService<Part> partStorageService;
	private StorageService<Computer> computerStorageService;
	private StorageService<Order> orderStorageService;

	private ObserveCallback<Part> partCallback;

	private ObserveCallback<Order> orderCallback;
	private ObserveCallback<Part> exportedPartCallback;
	private ObserveCallback<Order> exportedOrderCallback;

	public RmiAssemblyWorker(String factoryId) {
		super(factoryId);

		this.partStorageService = RMIManager.getStorageService(factoryId + "_" + Coma.PARTS_CONTAINER);
		this.computerStorageService = RMIManager.getStorageService(factoryId + "_" + Coma.COMPUTER_CONTAINER);
		this.orderStorageService = RMIManager.getStorageService(factoryId + "_" + Coma.ORDER_CONTAINER);
	}

	@Override
	public void startWork() {
		try {
			exportedPartCallback = new ObserveCallback<Part>() {
				@Override
				public void onChange(ChangeAction changeAction, List<Part> entries) throws RemoteException {
					if (!assemblyOrders()) {
						assemblyOrder(null);
					}
				}
			};
			partCallback = (ObserveCallback<Part>) UnicastRemoteObject.exportObject(exportedPartCallback, 0);
			partStorageService.registerObserveCallback(partCallback, ObserveCallback.ChangeAction.ADD);
			exportedOrderCallback = new ObserveCallback<Order>() {
				@Override
				public void onChange(ChangeAction changeAction, List<Order> entries) throws RemoteException {
					assemblyOrders();
				}
			};
			orderCallback = (ObserveCallback<Order>) UnicastRemoteObject.exportObject(exportedOrderCallback, 0);
			orderStorageService.registerObserveCallback(orderCallback, ObserveCallback.ChangeAction.ADD);
		} catch (RemoteException e) {
			e.printStackTrace();
		}
		if (!assemblyOrders()) {
			assemblyOrder(null);
		}
	}

	private boolean assemblyOrders() {
		boolean found = false;
		try {
			List<Order> orders = orderStorageService.getSnapshot(new SelectAllSelector<Order>());
			for (Order o : orders) {
				found = found | assemblyOrder(o);
			}
		} catch (RemoteException e) {
			log.error("Storage error", e);
			throw new RuntimeException(e);
		}
		return found;
	}

	private boolean assemblyOrder(Order order) {
		try {
			List<Part> partList = partStorageService.take(new ComputerPartSelector(order));
			if (partList == null || partList.isEmpty()) return false;
			Computer computer = new Computer(getWorkerId(), partList);
			computer.setOrder(order);
			computerStorageService.add(computer);
		} catch (RemoteException e) {
			log.error("Storage error", e);
			throw new RuntimeException(e);
		}
		return true;
	}

	@Override
	public void stopWork() {
		log.info("Stopping assembly worker");
		if (orderCallback != null) {
			try {
				UnicastRemoteObject.unexportObject(exportedOrderCallback, true);
			} catch (NoSuchObjectException e) {
				log.warn("Error unexporting callback", e);
			}
		}
		if (partCallback != null) {
			try {
				UnicastRemoteObject.unexportObject(exportedPartCallback, true);
			} catch (NoSuchObjectException e) {
				log.warn("Error unexporting callback", e);
			}
		}
	}

	@Override
	public boolean isActive() {
		return partCallback != null;
	}
}
